/** 
 *  @file
 *  @brief   ELAS1 单元 和 ELAS2 单元
 *  @author  kangrongyao
 *  @date    2024.05.30
 *  @version v1.0
 *  @par Copyright(c):  ShenGong  Simulation corporation
 */

#include "ELAS1Calculator.h"
#include "DBManager/IService/Input/Element/IElementService.h"
#include "DBManager/IService/Input/Property/IPELASService.h"
#include "DBManager/IService/Result/ElementalForce/IELAS1ForceService.h"
#include "DBManager/IService/Result/ElementalStrain/IELAS1StrainService.h"
#include "DBManager/IService/Result/ElementalStress/IELAS1StressService.h"
#include "SGFem/Pre/Assembly/Assembly.h"

using SG::Algebra::Matrixd;
using SG::DataStructure::Common::Status;
using SG::DataStructure::Common::Id;
using namespace SG::DataStructure::FEM;
using SG::DataStructure::Post::ELAS1ForceData;
using SG::DataStructure::Post::ELAS1StrainData;
using SG::DataStructure::Post::ELAS1StressData;
using namespace SG::DBManager;
using SG::FEMSolver::Element::ELAS1Calculator;

void ELAS1Calculator::Initialize (SG::DataStructure::FEM::ElementTypes type)
{
    m_type = type;
}

// 用匿名空间封装 GetELASElement()，防止同名函数
namespace 
{
    auto GetELASElement = [](const DBServiceFactorySP& dbServiceFactory, const ElementTypes& type, const std::size_t iEle, CELAS2Data& _OUT elasData) -> Status
    {
        if (ElementTypes::ELETYPE_ScalarSpring == type)
        {
            // 此时单元类型为CELAS1
            // 读取词条信息
            CELAS1Data tempElas;
            if (Status::STATUS_Failure == dbServiceFactory->get<IElementService> ()->FindByIndex (iEle, _OUT tempElas))
            {
                // todo: 错误日志
                return Status::STATUS_Failure;
            };
            // 读取属性信息
            PELASData tempPElas;
            if (Status::STATUS_Failure == dbServiceFactory->get<IPELASService> ()->FindById (tempElas.m_pId, _OUT tempPElas))
            {
                // todo: 错误日志
                return Status::STATUS_Failure;
            }
            elasData.m_id   = tempElas.m_id;
            elasData.m_K    = tempPElas.m_K;
            elasData.m_g[0] = tempElas.m_g[0];
            elasData.m_g[1] = tempElas.m_g[1];
            elasData.m_C1   = tempElas.m_c1;
            elasData.m_C2   = tempElas.m_c2;
            elasData.m_GE   = tempPElas.m_D;
            elasData.m_S    = tempPElas.m_S;
        }
        else
        {
            // 此时单元类型为CElas2,直接写入elasData即可
            if (Status::STATUS_Failure == dbServiceFactory->get<IElementService>()->FindByIndex (iEle, _OUT elasData))
            {
                // todo: 错误日志
                return Status::STATUS_Failure;
            }
        }

        return Status::STATUS_Success;
    };
}

void ELAS1Calculator::ComputeStiffness (const SG::DataStructure::Common::Real k, _OUT SG::Algebra::Matrixd& kg)
{
    kg = Matrixd (2, 2, {k, -k, -k, k});
}

void ELAS1Calculator::ComputeElementMatries (const SG::DBManager::DBServiceFactorySP dbServiceFactory)
{
    auto& assembly = AssemblyEigen::GetInstance ();

    CELAS2Data element;
    std::vector<std::size_t> nodeIndexSet (2);
    std::vector<int> dofGlobalIndexSet (2);   // 非接地时使用
    std::vector<int> singleGlobalIndex (1);   // 接地时使用
    Matrixd kg (2, 2);                        // 非接地时使用
    Matrixd kgSinglePoint (1, 1);             // 接地时使用
    
    const auto& elementService = dbServiceFactory->get<IElementService>();
    const auto eleSize = elementService->GetElementSize (m_type);
    for (std::size_t iEle{0}; iEle < eleSize; ++iEle)
    {
        // 根据不同类型，获取单元数据
        GetELASElement(dbServiceFactory, m_type, iEle, _OUT element);


        // 计算单元刚度矩阵
        ComputeStiffness (element.m_K, _OUT kg); 
        kgSinglePoint(0, 0) = kg(0, 0); // 接地时使用

        if(element.m_g[1] > 0)
        {
            // 获取单元节点对应的索引
            assembly.FindNodeIndex (element.m_g, _OUT nodeIndexSet);
            
            // 获取单元自由度对应的全局自由度索引
            assembly.GetGlobalDOFId (nodeIndexSet[0], element.GetDOF1 (), _OUT dofGlobalIndexSet[0]);
            assembly.GetGlobalDOFId (nodeIndexSet[1], element.GetDOF2 (), _OUT dofGlobalIndexSet[1]);
            
            // ToDo 增加对返回值判断
            
            // 组装单元刚度矩阵
            assembly.AccumulateStiffness (dofGlobalIndexSet, kg);
        }
        else
        {
            // m_g[1] = 0, 此时弹簧接地
            assembly.FindNodeIndex (element.m_g[0], _OUT nodeIndexSet[0]);
            assembly.GetGlobalDOFId (nodeIndexSet[0], element.GetDOF1 (), _OUT singleGlobalIndex[0]);
            assembly.AccumulateStiffness (singleGlobalIndex, kgSinglePoint);
        }
    }
}

void ELAS1Calculator::ComputeElementInternalForce
(
const SG::DataStructure::Common::Real& k,
const SG::Algebra::Matrixd&            elementDisplacement,
_OUT SG::DataStructure::Common::Real&  elementInternalForce
)
{
    elementInternalForce = k * (elementDisplacement (0, 0) - elementDisplacement (1, 0));
}

void ELAS1Calculator::ComputeElementStrain
(
const SG::DataStructure::Common::Real& S,
const SG::Algebra::Matrixd&            elementDisplacement,
_OUT SG::DataStructure::Common::Real&  elementStrain
)
{
    elementStrain = S * (elementDisplacement (0, 0) - elementDisplacement (1, 0));
}

void ELAS1Calculator::ComputeElementStress
(
const SG::DataStructure::Common::Real& S,
const SG::DataStructure::Common::Real& elementInternalForce,
_OUT SG::DataStructure::Common::Real&  elementStress
)
{
    elementStress = S * elementInternalForce;
}

// 后处理数据
void ELAS1Calculator::DataRecovery
(
const SG::DBManager::DBServiceFactorySP             dbServiceFactory,
int                                                 subcaseId, 
SG::DataStructure::Common::Real time,
const std::vector<SG::DataStructure::Common::Real>& globalDisplacement
)
{
    const auto& assembly = AssemblyEigen::GetInstance ();
    
    const auto& elementService = dbServiceFactory->get<IElementService>();
    const auto eleSize = elementService->GetElementSize (m_type);
    ELAS1ForceData eleForceData;
    std::vector<ELAS1ForceData> eleForceDataSet (eleSize);
    ELAS1StrainData eleStrainData;
    std::vector<ELAS1StrainData> eleStrainDataSet (eleSize);
    ELAS1StressData eleStressData;
    std::vector<ELAS1StressData> eleStressDataSet (eleSize);

    CELAS2Data element;
    std::vector<std::size_t> nodeIndexSet (2);
    std::vector<int> dofGlobalIndexSet (2);

    // 对 Elas1 类型单元进行遍历
    for (std::size_t iEle{0}; iEle < eleSize; ++iEle)
    {
        GetELASElement(dbServiceFactory, m_type, iEle, _OUT element);
        
        // 获取单元节点对应的索引
        assembly.FindNodeIndex (element.m_g, _OUT nodeIndexSet);

        // 求解单元位移
        Matrixd eleDisplacement (2, 1);
        
        if (element.m_g[1] > 0)
        {
        // 获取单元自由度对应的全局自由度索引
        assembly.GetGlobalDOFId (nodeIndexSet[0], element.GetDOF1 (), _OUT dofGlobalIndexSet[0]);
        assembly.GetGlobalDOFId (nodeIndexSet[1], element.GetDOF2 (), _OUT dofGlobalIndexSet[1]);

        // 从总体位移向量中提取单元位移
        eleDisplacement (0, 0) = globalDisplacement[dofGlobalIndexSet[0]];
        eleDisplacement (1, 0) = globalDisplacement[dofGlobalIndexSet[1]];
        }
        else
        {
            assembly.GetGlobalDOFId (nodeIndexSet[0], element.GetDOF1 (), _OUT dofGlobalIndexSet[0]);
            eleDisplacement (0, 0) = globalDisplacement[dofGlobalIndexSet[0]];
            eleDisplacement (1, 0) = 0.0;
        }

        // 计算单元内力，并存储
        eleForceData.m_eId = element.m_id;
        ComputeElementInternalForce (element.m_K, eleDisplacement, _OUT eleForceData.m_F);
        eleForceDataSet[iEle] = eleForceData;

        // 计算单元应变，并存储
        eleStrainData.m_eId = element.m_id;
        ComputeElementStrain (element.m_S, eleDisplacement, _OUT eleStrainData.m_S);
        eleStrainDataSet[iEle] = eleStrainData;
        
        // 计算单元应力，并存储
        eleStressData.m_eId = element.m_id;
        ComputeElementStress (element.m_S, eleForceData.m_F, _OUT eleStressData.m_S);
        eleStressDataSet[iEle] = eleStressData;
    }

    
    if (ElementTypes::ELETYPE_ScalarSpring == m_type)
    {
        dbServiceFactory->get<IELAS1ForceService> ()->AddBySubcaseId (static_cast<Id> (subcaseId), eleForceDataSet);
        dbServiceFactory->get<IELAS1StrainService> ()->AddBySubcaseId (static_cast<Id> (subcaseId), eleStrainDataSet);
        dbServiceFactory->get<IELAS1StressService> ()->AddBySubcaseId (static_cast<Id> (subcaseId), eleStressDataSet);
    }
    else
    {
            // to do;
    }
}

// 计算全局坐标系下单元应力刚度矩阵
void ELAS1Calculator::ComputeStressStiffness (const SG::DBManager::DBServiceFactorySP dbServiceFactory, int staticSubId)
{
}